/*
	Adobe Systems Incorporated(r) Source Code License Agreement
	Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
	
	Please read this Source Code License Agreement carefully before using
	the source code.
	
	Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive, 
	no-charge, royalty-free, irrevocable copyright license, to reproduce,
	prepare derivative works of, publicly display, publicly perform, and
	distribute this source code and such derivative works in source or 
	object code form without any attribution requirements.  
	
	The name "Adobe Systems Incorporated" must not be used to endorse or promote products
	derived from the source code without prior written permission.
	
	You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
	against any loss, damage, claims or lawsuits, including attorney's 
	fees that arise or result from your use or distribution of the source 
	code.
	
	THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT 
	ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
	FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  ALSO, THERE IS NO WARRANTY OF 
	NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT.  IN NO EVENT SHALL MACROMEDIA
	OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
	OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
	WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
	OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.adobe.webapis.mappr 
{
	import com.adobe.webapis.URLLoaderBase;
	
	import flash.net.URLLoader;
	
	import flash.net.URLVariables;
	import flash.net.URLRequest;
	
	import flash.events.Event;
	
	import com.adobe.webapis.mappr.Place;
	import com.adobe.webapis.mappr.Considerations;
	import com.adobe.webapis.mappr.Position;
	import com.adobe.webapis.mappr.Match;
	import com.adobe.webapis.mappr.Photo;
	
	import com.adobe.webapis.mappr.events.MapprResultEvent;
	import com.adobe.webapis.mappr.events.MapprServiceErrorEvent;
	
	/**
	* 	Broadcast when results have been loaded from Mappr in response to
	* 	getPhotoInfo() being called.
	*
	* 	The event contains the following properties		
	* 	data.photo A Photo instance with information about the specified photo.	
	*
	* 	@langversion ActionScript 3.0
	*	@playerversion Flash 8.5
	*	@tiptext
	* 
	* 	@eventType com.adobe.webapis.mappr.events.MapprResultEvent.ON_GET_PHOTO_INFO
	* 
	* 	@see #getPhotoInfo
	* 	@see #com.adobe.webapis.mappr.Photo
	*/
	[Event(name="onGetPhotoInfo", 
		 type="com.adobe.webapis.mappr.events.MapprResultEvent")]		
	
	/**
	* 	Broadcast when results have been loaded from Mappr in response to
	* 	match() being called.
	*
	* 	The event contains the following properties		
	* 	data.places An Array of Place ojects.	
	*
	* 	@langversion ActionScript 3.0
	*	@playerversion Flash 8.5
	*	@tiptext
	* 
	* 	@eventType com.adobe.webapis.mappr.events.MapprResultEvent.ON_MATCH_RESULT
	* 
	* 	@see #match
	* 	@see #com.adobe.webapis.mappr.Place
	*/
	[Event(name="onMatchResult", 
		 type="com.adobe.webapis.mappr.events.MapprResultEvent")]		
	
	/**
	* 	Broadcast when results have been loaded from Mappr in response to
	* 	gePlaceInfo() being called.
	*
	* 	The event contains the following properties		
	* 	data.places An array of Place instances with information about the specified place.	
	*
	* 	@langversion ActionScript 3.0
	*	@playerversion Flash 8.5
	*	@tiptext
	* 
	* 	@eventType com.adobe.webapis.mappr.events.MapprResultEvent.ON_GET_PLACE_INFO
	* 
	* 	@see #getPlaceInfo
	* 	@see #com.adobe.webapis.mappr.Place
	*/
	[Event(name="onGetPlaceInfo", 
		 type="com.adobe.webapis.mappr.events.MapprResultEvent")]		
	
	
	/**
	* 	Broadcast when results have been loaded from Mappr in response to
	* 	getRecent() being called.
	*
	* 	The event contains the following properties		
	* 	data.photos An Array of Photo instances.	
	* 	data.perPage Number of items displayed per page.
	* 	data.page Current page set of Photos that the result represents.
	*
	* 	@langversion ActionScript 3.0
	*	@playerversion Flash 8.5
	*	@tiptext
	* 
	* 	@eventType com.adobe.webapis.mappr.events.MapprResultEvent.ON_GET_RECENT
	* 
	* 	@see #getRecent
	* 	@see #com.adobe.webapis.mappr.Photo
	*/
	[Event(name="onGetRecent", 
		 type="com.adobe.webapis.mappr.events.MapprResultEvent")]	
	
	/**
	* 	Broadcast when results have been loaded from Mappr in response to
	* 	search() being called.
	*
	* 	The event contains the following properties		
	* 	data.photos An Array of Photo instances.	
	* 	data.perPage Number of items displayed per page.
	* 	data.page Current page set of Photos that the result represents.
	*
	* 	@langversion ActionScript 3.0
	*	@playerversion Flash 8.5
	*	@tiptext
	* 
	* 	@eventType com.adobe.webapis.mappr.events.MapprResultEvent.ON_SEARCH
	* 
	* 	@see #search
	* 	@see #com.adobe.webapis.mappr.Photo
	*/
	[Event(name="onSearch", 
		 type="com.adobe.webapis.mappr.events.MapprResultEvent")]		
	
	
	/**
	* 	Broadcast if the Mappr returns an error in response to an API call.
	*
	* 	@langversion ActionScript 3.0
	*	@playerversion Flash 8.5
	*	@tiptext
	* 
	* 	@eventType com.adobe.webapis.mappr.events.MapprServiceErrorEvent.ON_API_ERROR
	*/
	[Event(name="onAPIError", 
		 type="com.adobe.webapis.mappr.events.MapprServiceErrorEvent")]	
	
	/**
	*	Class that provides an ActionScript 3 interface to the remote
	*	Mappr API
	*
	* 	@langversion ActionScript 3.0
	*	@playerversion Flash 8.5
	*	@tiptext
	* 
	*	@author Mike Chambers, Adobe Systems Incorporated
	* 
	* 	@see http://www.mappr.com/about/api.phtml	
	*/
	public class MapprService extends URLLoaderBase
	{
		private var apiKey:String;
		private const BASE_URL:String = "http://www.mappr.com/services/";
		private const MAPPR_PLACE_MATCH:String = "mappr.place.match";
		private const MAPPR_GET_PLACE_INFO:String = "mappr.place.getInfo";
		private const MAPPR_GET_RECENT:String = "mappr.photos.getRecent";
		private const MAPPR_SEARCH:String = "mappr.photos.search";
		private const MAPPR_GET_PHOTO_INFO:String = "mappr.photos.getInfo";
				
		/**
		*	Constructor for Mappr class.
		*
		* 	@langversion ActionScript 3.0
		*	@playerversion Flash 8.5
		*	@tiptext
		* 
		*	@param apiKey The Mappr developer API necessary to use
		*	the Mappr API service. You can get a key from http://www.mappr.com
		*/
		public function MapprService(apiKey:String)
		{
			this.apiKey = apiKey;
		}		
		
		/**
		* 	Retrieves information on the specified photo
		* 
		* 	@langversion ActionScript 3.0
		*	@playerversion Flash 8.5
		*	@tiptext
		* 
		* 	@param photo_id The id of the photo to fetch information for.
		* 	@param reconsider Either false to pull information from Mappr 
		* 	without checking Flickr, or true to reevaluate tags and location
		* 	guess, a slightly more resource-intensive process. Use with care.
		*/ 
		public function getPhotoInfo(photo_id:String, reconsider:Boolean = false):void
		{
			var loader:URLLoader = getURLLoader();
				loader.addEventListener("complete", onGetPhotoInfo);
				
			var urlVars:URLVariables = getURLVariables(MAPPR_GET_PHOTO_INFO);
			
				urlVars.photo_id = photo_id;
				urlVars.reconsider = (reconsider)?"yes":"no";

			var request:URLRequest = getURLRequest(urlVars);
			
			loader.load(request);			
		}
		
		/* event handler for getPhotoInfo api */
		private function onGetPhotoInfo(event:Event):void
		{
			var x:XML = new XML(event.target.data);		
			
			//check to see if the returned XML represents
			//an error
			if(checkForError(x))
			{
				return;
			}			
			
			var r:MapprResultEvent = new MapprResultEvent(MapprResultEvent.ON_GET_PHOTO_INFO);
			
			r.data.photo = parsePhotoXML(x.photo[0]);
			
			dispatchEvent(r);				
		}
		
		/**
		*	Retrieves a geographical place representing the
		*	place ID specified.
		*
		* 	@langversion ActionScript 3.0
		*	@playerversion Flash 8.5
		*	@tiptext
		* 
		*	@param placeId Place ID to retrieve information from. This
		*	is usually obtained from the Place object retrieved from the
		*	match call.
		*
		*	@see #match
		*	@see #com.adobe.webapis.mappr.Place
		*	@see http://www.mappr.com/about/api.phtml#mappr.place.getRecent
		*/		
		public function getRecent(placeId:String = null, perPage:uint = 10, 
										page:uint = 1, group:Boolean = true):void
		{
			var loader:URLLoader = getURLLoader();
				loader.addEventListener("complete", onGetRecent);
				
			var urlVars:URLVariables = getURLVariables(MAPPR_GET_RECENT);
			
			if(placeId != null)
			{
				urlVars.place_id = placeId;
			}
				urlVars.per_page = perPage;
				urlVars.page = page;
				urlVars.group_by = (group)?"yes":"no";

			var request:URLRequest = getURLRequest(urlVars);
			
			loader.load(request);	
		}
		
		/* event handler for getRecent api */
		private function onGetRecent(event:Event):void
		{
			var x:XML = new XML(event.target.data);
			
			//check to see if the returned XML represents
			//an error
			if(checkForError(x))
			{
				return;
			}			
			
			var r:MapprResultEvent = new MapprResultEvent(MapprResultEvent.ON_GET_RECENT);
			
			r.data.page = Number(x.photos.@page);
			r.data.perPage = Number(x.photos.@per_page);
			r.data.photos = parsePhotosXML(x.photos.photo);
			
			dispatchEvent(r);				
		}
	
		
		/**
		*	Retrieves a geographical place representing the
		*	place ID specified.
		*
		* 	@langversion ActionScript 3.0
		*	@playerversion Flash 8.5
		*	@tiptext
		* 
		*	@param placeId Place ID to retrieve information from. This
		*	is usually obtained from the Place object retrieved from the
		*	match call.
		*
		*	@see #match
		*	@see #com.adobe.webapis.mappr.Place
		*	@see http://www.mappr.com/about/api.phtml#mappr.place.getInfo
		*/
		public function getPlaceInfo(placeId:String):void
		{
			var loader:URLLoader = getURLLoader();
				loader.addEventListener("complete", onGetPlaceInfo);
				
			var urlVars:URLVariables = getURLVariables(MAPPR_GET_PLACE_INFO);
				urlVars.place_id = placeId;

			var request:URLRequest = getURLRequest(urlVars);
			
			loader.load(request);		
		}
		
		/* event handler for getPlaceInfo api */
		private function onGetPlaceInfo(event:Event):void
		{
			var x:XML = new XML(event.target.data);
			
			//check to see if the returned XML represents
			//an error
			if(checkForError(x))
			{
				return;
			}
			
			var r:MapprResultEvent = new MapprResultEvent(MapprResultEvent.ON_GET_PLACE_INFO);
			
			r.data.places = parsePlacesXML(x.place);
			
			dispatchEvent(r);			
		}
		
		/**
		*	Retrieves an Array of Place objects that contain
		*	information about the locations specified in the 
		*	parameters
		*
		* 	@langversion ActionScript 3.0
		*	@playerversion Flash 8.5
		*	@tiptext
		* 
		*	@param tags An Array of locations to match.
		*	@param limit The maximum number of Places to return. The default is 3.
		*
		*	@see http://www.mappr.com/about/api.phtml#mappr.place.match
		*/
		public function match(tags:Array, limit:uint = 3 ):void
		{
			//should we check the length?
			//check null?
			
			var loader:URLLoader = getURLLoader();
				loader.addEventListener("complete", onMatch);
				
			var urlVars:URLVariables = getURLVariables(MAPPR_PLACE_MATCH);
				
			var len:uint = tags.length;
			
			for(var i:uint = 0; i < len; i++)
			{
				urlVars["tag" + String(i)] = tags[i];
			}
			
			var request:URLRequest = getURLRequest(urlVars);
			
			loader.load(request);
		}
		
		/* Event handler for MapprResultEvent.ON_MATCH_RESULT event */
		private function onMatch(event:Event):void
		{
			var x:XML = new XML(event.target.data);
			
			//check to see if the returned XML represents
			//an error
			if(checkForError(x))
			{
				return;
			}
						
			var r:MapprResultEvent = new MapprResultEvent(MapprResultEvent.ON_MATCH_RESULT);
			
			r.data.places = parsePlacesXML(x.place);
			
			dispatchEvent(r);
		}

		/**
		* 	Retrieves photos based on the specified parameters.
		* 
		* 	@param user_id The NSID of the user who's photo to 
		* 	search. If this parameter isn't passed then everybody's
		* 	public photos will be searched.
		* 
		* 	@langversion ActionScript 3.0
		*	@playerversion Flash 8.5
		*	@tiptext
		* 
		* 	@param tag A tag. Photos with the tag listed will be returned.
		* 
		* 	@param place_id A place ID. Photos in this place will be returned.
		* 
		* 	@param per_page Number of photos to return per page.
		* 
		* 	@param page The page of results to return. 
		* 
		* 	@param group Either true to show one photo per place 
		* 	(depending on other parameters, e.g. omitting 'place_id' will
		* 	return one photo per place), or false to show all photos 
		* 	ungrouped.
		* 
		* 	@see http://www.mappr.com/about/api.phtml#mappr.photos.search
		*/
		public function search(tag:String,
								user_id:String = null,
								place_id:String = null,
								per_page:uint = 100,
								page:uint = 1,
								group:Boolean = true):void
		{
			var loader:URLLoader = getURLLoader();
				loader.addEventListener("complete", onSearch);
				
			var urlVars:URLVariables = getURLVariables(MAPPR_SEARCH);
				
				if(user_id != null)
				{
					urlVars.user_id = user_id;
				}
				
				if(place_id != null)
				{
					urlVars.place_id = tag;
				}
				
				urlVars.tag = tag;
				urlVars.per_page = per_page;
				urlVars.page = page;
				urlVars.group_by = (group)?"yes":"no";
			
			var request:URLRequest = getURLRequest(urlVars);
			
			loader.load(request);
		}
		
		/* event handler for search api */
		private function onSearch(event:Event):void
		{
			var x:XML = new XML(event.target.data);			
			
			//check to see if the returned XML represents
			//an error
			if(checkForError(x))
			{
				return;
			}
			
			var r:MapprResultEvent = new MapprResultEvent(MapprResultEvent.ON_SEARCH);	
			
			r.data.page = Number(x.photos.@page);
			r.data.perPage = Number(x.photos.@per_page);
			r.data.photos = parsePhotosXML(x.photos.photo);
			
			dispatchEvent(r);			
		}
		
		/* broadcasts an MapprServiceErrorEvent.ON_API_ERROR event */
		private function broadcastError(code:uint, message:String):void
		{
			var e:MapprServiceErrorEvent = new MapprServiceErrorEvent(MapprServiceErrorEvent.ON_API_ERROR);
				e.code = code;
				e.message = message;
				
			dispatchEvent(e);
		}		
		
		/******************* Class Specific Utility APIs *************************/
		
		private function checkForError(x:XML, broadcast:Boolean = true):Boolean
		{	
			//check to see if the err tag exists
			
			var s:int = x.err.length();
			
			if(x.err.length() == 0)
			{
				return false;
			}
			
			if(broadcast)
			{
				broadcastError(Number(x.err.@code),
								String(x.err.@msg));
			}
			
			return true;
		}

		/* parses Mapprs Date / Time format into an ActionScript Date instance */
		private function parseDate(date:String, time:String):Date
		{
			//format
			//date="Dec 28 2004" time="19:39:49"
			var d:Array = date.split(" ");
			var t:Array = time.split(":");
			
			import com.adobe.utils.DateUtil;
			
			//yearOrTimevalue:Object, month:Number, date:Number = 1, hour:Number = 0, minute:Number = 0, second:Number = 0
			var oDate:Date = new Date(d[2],
										DateUtil.getShortMonthIndex(d[0]),
										d[1],
										t[0],
										t[1],
										t[2]);
			return oDate;
		}
				
		
		/* Creates the URLVariables object used by the API */
		private function getURLVariables(method:String):URLVariables
		{
			var urlVars:URLVariables = new URLVariables();
				urlVars.api_key = apiKey;
				urlVars.method = method;
				
			return urlVars;
		}
		
		/* generates a URL request with the specified url variables */
		private function getURLRequest(urlVars:URLVariables):URLRequest
		{
			var request:URLRequest = new URLRequest(BASE_URL);
			request.data = urlVars;	
			return request; 
		}
		
		/*******************Private Parsing APIs *************************/
		
		/* parses an array of photo xml elements */
		private function parsePhotosXML(pList:XMLList):Array
		{

			var len:uint = pList.length();
			
			var pArray:Array = new Array();
			
			for(var i:uint = 0; i < len; i++)
			{
				pArray.push(parsePhotoXML(pList[i]));		
			}
			
			return pArray;
		}
		
		/* parses an individual photo xml element */
		private function parsePhotoXML(x:XML):Photo
		{	
			var p:Photo = new Photo();
				p.id = String(x.@id);
				p.thumbnail = String(x.@thumbnail);
		
				p.date = parseDate(String(x.@date), String(x.@time)); 
				p.link = String(x.@link);
				
				p.place = parseFlatPlaceXML(x.place[0]);
				
				p.match = parseMatchXML(x.match[0]);
				p.projected = parseProjectedXML(x.projected[0]);
				p.owner = parseOwnerXML(x.owner[0]);
				
				p.tags = parseTagsXML(x.tags.tag);
				
			return p;
		}
		
		/* parses the Tags XML element */
		private function parseTagsXML(xList:XMLList):Array
		{
			var a:Array = new Array();
			
			var len:uint = xList.length();
			
			var t:Tag;
			for(var i:uint = 0; i < len; i++)
			{
				t = new Tag();
				t.name = String(xList[i]);
				t.count = uint(xList[i].@id);
				a.push(t);
			}
			
			return a;
		}		
		
		/* this is used to parse the flat place xml in the API */
		private function parseFlatPlaceXML(x:XML):Place
		{
			//we dont use the other parse place function, because
			//the place XML returned from this call, is different that
			//that returned from other calls	
			var place:Place = new Place();
				place.id = String(x.@id);
				place.name = String(x.@["name"]);	
				
			var pos:Position = new Position();
				pos.latitude = String(x.@latitude);	
				pos.longitude = String(x.@longitude);
				pos.spread = String(x.@spread);
				
				place.position = pos;	
				
			return place;		
		}
		
		/* parses projected xml tag */
		private function parseProjectedXML(x:XML):Projected
		{				
			var projected:Projected = new Projected();
				projected.x = Number(x.@x);
				projected.y = Number(x.@y);
				projected.theta = Number(x.@theta);
				projected.spread = Number(x.@spread);	
				
			return projected;
		}
		
		/* parse owner xml element */
		private function parseOwnerXML(x:XML):Owner
		{
			if(x == null)
			{
				return null;
			}
			
			
			var owner:Owner = new Owner();
				owner.icon = String(x.@icon);
				owner.link = String(x.@link);
				owner.nsid = String(x.@nsid);
				owner.username = String(x.@username);
				
			return owner;
		}		
		
		/* parses list of places tag */
		private function parsePlacesXML(x:XMLList):Array
		{
			var pArray:Array = new Array();
			
			var len:uint = x.length();
			
			for(var i:uint = 0; i < len; i++)
			{
				pArray.push(parsePlaceXML(x[i]));
			}
			
			return pArray;
		}
		
		/* parses place XML tag */
		private function parsePlaceXML(x:XML):Place
		{
			if(x == null)
			{
				return null;
			}
			
			var p:Place = new Place();
			
			var pName:String = String(x["name"]);
			
			if(pName == "")
			{
				pName = String(x);
			}
			
			p.name = pName;
			
			p.fullName = String(x.fullname);
			p.provenance = String(x.@provenance);
			
			p.match = parseMatchXML(x.match[0]);
			
			p.id = String(x.@id);
			p.type = String(x.@type);
			
			p.position = parsePositionXML(x.position[0]);
			p.considerations = parseConsiderationsXML(x.considerations[0]);
			
			p.lineage = parseLineageXML(x.lineage[0]);
				
			return p;
		}
		
		/* parses match XML tag */
		private function parseMatchXML(x:XML):Match
		{
			if(x == null)
			{
				return null;
			}

			var m:Match = new Match();
				m.votes = Number(x.@votes);
				
			return m;
		}
		
		/* parses position xml tag */
		private function parsePositionXML(x:XML):Position
		{	
			if(x == null)
			{
				return null;
			}
			
			var p:Position = new Position();
			
				p.latitude = String(x.@latitude);
				
				p.longitude = String(x.@longitude);
				p.knowledge = String(x.@knowledge);
				p.spread = String(x.@spread);
				
			return p;
		}
		
		/* parses considerations XML tag */
		private function parseConsiderationsXML(x:XML):Considerations
		{
			if(x == null)
			{
				return null;
			}
			
			var c:Considerations = new Considerations();
				c.famous = (Number(x.@famous) == 1)? true : false;
				c.word = (Number(x.@word) == 1)? true : false;
				c.babyName = (Number(x.@babyname) == 1)? true : false;
				
			return c;
		}
		
		/* parses lineage xml tag */
		private function parseLineageXML(x:XML):Array
		{	
			if(x == null)
			{
				return null;
			}
			
			var xList:XMLList = x.place;
			
			var len:uint = xList.length();
			
			var a:Array = new Array();
			
			for(var i:uint = 0; i < len; i++)
			{
				a.push(parsePlaceXML(xList[i]));
			}
			
			return a;
		}
	}
}